﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Media.DirectShow.Filters;
using System.Windows.Media.DirectShow;
using System.Linq;
using System.IO;
using System.Collections.Generic;
using System.Diagnostics;

namespace System.Windows.Media.DirectShow.Avi
{
    public class AviSourceSplitter : MediaSourceAsync
    {

        private AviFileParser parser;

        public AviSourceSplitter()
        {

        }

        public override string Name
        {
            get { return "AVI Splitter"; }
        }

        protected override void RunInternal(TimeSpan start)
        {
            base.RunInternal(start);

            this.QueueWork(SendPackets);
        }

        private void SendPackets(object s)
        {
            AviPacket ap;
            while (this.State != FilterState.Stopped && (ap = parser.ReadNextPacket()) != null)
            {
                BaseOutputPin pin = (BaseOutputPin)this.Pins[ap.StreamIndex];
                // Only if pin is connected
                if (pin.ConnectedPin != null)
                    pin.ProcessStream(ap.Stream, ap.StartOffset, (int)(ap.EndOffset - ap.StartOffset));
            }

            if (this.State != FilterState.Stopped)
            {
                // Flush and send end of stream
                this.Pins.ExecuteAction<BaseOutputPin>(p =>
                {
                    p.Flush();
                    p.DeliverEndOfStream();
                });
            }
        }

        protected override void OnStreamAvailable(Stream stream)
        {
            base.OnStreamAvailable(stream);

            parser = new AviFileParser(new BinaryReader(stream));
            AviFile file = parser.Parse();

            // Remove old pins
            this.Pins.ExecuteAction<IPin>(p => p.Disconnect());
            this.Pins.Clear();

            IList<AviChunk> chunks = file.Lists.ToFlat(p => p.Lists).SelectMany(c => c.Chunks).ToList();

            for (int x = 0; x < chunks.Count; x++)
            {
                ParseChunk(chunks, x);
            }
        }

        private void ParseChunk(IList<AviChunk> chunks, int x)
        {
            AviStreamHeader header = chunks[x].Value as AviStreamHeader;
            if (header != null)
            {
                IMediaTypeFormat format = chunks[x + 1].Value as IMediaTypeFormat;
                if (format == null)
                    throw new InvalidOperationException("Unknown AVI header format");
                AMMediaType m = new AMMediaType();
                m.FourCC = header.Handler;
                m.Format = format;
                m.SampleSize = header.SampleSize;

                IPin pin = null;

                switch ((string)header.Type)
                {
                    case "vids":
                        m.MajorType = MediaMajorTypes.Video;
                        switch (((string)header.Handler).ToUpper().Trim())
                        {
                            case "ARGB":
                            case "DIB":
                                m.FourCC = "RGBA";
                                m.SubType = MediaSubTypes.ARGB;
                                break;
                            case "\0\0\0\0":
                                ParseRGB(m, (BitmapInfoHeader)format);
                                break;
                            default:
                                m.SubType = MediaSubTypes.Unknown;
                                break;
                        }

                        pin = new AviSourceSplitterVideoOutputPin(this, m);

                        break;
                    case "auds":
                        m.MajorType = MediaMajorTypes.Audio;
                        switch (((string)header.Handler).ToUpper().Trim())
                        {
                            case "\0\0\0\0":
                            case "PCM":
                                m.FourCC = "PCM";
                                m.SubType = MediaSubTypes.PCM;
                                break;
                            default:
                                // PCM: sometimes handler is not specified
                                if (m.Format is WaveFormatExtensible && ((WaveFormatExtensible)m.Format).FormatTag == 1)
                                {
                                    m.SubType = MediaSubTypes.PCM;
                                    m.FourCC = "PCM";
                                }
                                else
                                    m.SubType = MediaSubTypes.Unknown;

                                break;
                        }

                        pin = new AviSourceSplitterAudioOutputPin(this, m);
                        break;
                    default:
                        throw new InvalidOperationException("Unknown header type");
                }

                if (pin != null)
                    this.Pins.Add(pin);
            }
        }

        private static void ParseRGB(AMMediaType m, BitmapInfoHeader format)
        {
            if (format.BitCount == 24)
            {
                m.FourCC = "RGB24";
                m.SubType = MediaSubTypes.RGB24;
            }
            // TODO: other
        }
    }
}
